package cn.seaboot.flake.reverse;

import cn.seaboot.commons.core.ClassPathResource;
import cn.seaboot.commons.core.Converter;
import cn.seaboot.flake.entity.NormColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;

/**
 * 默认的数据库类型转换工具（MySQL）
 *
 * @author ChenSS on 2018/4/28.
 */
public class JdbcTypeConverter implements Function<NormColumn, String> {

    /**
     * jdbc 类型映射配置文件，放到 resources 根目录即可
     */
    private static final String CONFIG = "jdbc-type-mapping.properties";

    /**
     * 缓存一部分的类型，避免过多的类型匹配算法
     */
    private final Map<String, String> cache = new HashMap<>();

    /**
     * jdbc 可选类型
     */
    private final Map<String, JdbcTypeMapping> mapping = new HashMap<>();

    public JdbcTypeConverter() {
        this.init();
    }

    /**
     * 载入 properties 文件的配置信息
     */
    private void init() {
        try {
            Properties properties = new Properties();
            properties.load(ClassPathResource.getInputStream(CONFIG));

            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                String key = Converter.toString(entry.getKey()).toLowerCase();
                String value = Converter.toString(entry.getValue());
                String[] values = value.split(",");

                JdbcTypeMapping map = new JdbcTypeMapping();
                map.setType(key);
                map.setSuggest(values[0]);
                map.setOthers(values);
                mapping.put(key, map);
            }
        } catch (IOException e) {
            // just log record current error, will using default setting!
            Logger logger = LoggerFactory.getLogger(JdbcTypeConverter.class);
            logger.error("load failed: jdbc-type-mapping.properties!", e);
        }
    }

    /**
     * 获取对应的 java-type
     *
     * @param column 标准列
     * @return java类型
     */
    public String apply(NormColumn column) {
        String jdbcType = column.getJdbcType().toLowerCase();

        // 对于这个类型，特殊处理一下
        if ("number".equals(jdbcType)) {
            if (column.getNumericScale() != 0) {
                return "java.math.BigDecimal";
            } else if (column.getNumericPrecision() > 10) {
                return "Long";
            } else {
                return "Integer";
            }
        }

        return this.javaType(jdbcType);
    }

    /**
     * 根据Jdbc类型，获取默认的Java类型
     *
     * @param jdbcType jdbc类型
     * @return java类型
     */
    public String javaType(String jdbcType) {
        String res = cache.get(jdbcType);
        if (res == null) {
            JdbcTypeMapping ret = mapping.get(jdbcType);
            if (ret != null) {
                res = ret.getSuggest();
            } else {
                res = this.mysql(jdbcType);
            }
            cache.put(jdbcType, res);
        }
        return res;
    }

    /**
     * 标准 mysql 字段类型映射（包含部分 oracle）
     * <p>
     * 市场上，大部分会这么处理映射，但是这并不一定符合个人编码习惯。
     *
     * @param jdbcType jdbc类型
     * @return java类型
     */
    private String mysql(String jdbcType) {
        switch (jdbcType) {
            case "varchar":
            case "varchar2":
            case "nvarchar":
            case "char":
            case "text":
            case "longtext":
                return "java.lang.String";
            case "int":
                return "int";
            case "bit":
                return "java.lang.Boolean";
            case "numeric":
                return "java.math.BigDecimal";
            case "tinyint":
                return "java.lang.Byte";
            case "smallint":
                return "java.lang.Short";
            case "integer":
                return "java.lang.Integer";
            case "bigint":
                return "java.math.BigInteger";
            case "real":
            case "float":
                return "java.lang.Float";
            case "double":
            case "number":
                return "java.lang.Double";
            case "date":
                return "java.sql.Date";
            case "time":
                return "java.sql.Time";
            case "datetime":
            case "timestamp":
                return "java.sql.Timestamp";
            case "varbinary":
            case "binary":
                return "byte[]";
            case "clob":
                return "java.sql.Clob";
            case "blob":
                return "java.sql.Blob";
            case "array":
                return "java.sql.Array";
            case "ref":
                return "java.sql.Ref";
            case "struct":
                return "java.sql.Struct";
            case "point":
                return "java.awt.Point";
            default:
                return "java.lang.String";
        }
    }
}